u"""
Author: TheAtilla
Package for controlling/reading Thinkpad hardware goodies.
Includes fan speed control, thermal sensor readings, etc..

TODO: Figure out model-specific functionality and add configuration/control
accordingly. See if it's possible to do model-detection for automatic config loading.
"""


class ThinkpadControl:
    u"""
    Base class for all thinkpad laptop controls. 
    """

    _controlFile = None
    _valueRange = None

    def __init__(self, controlFilename, valueRange):
        u"""
        controlFilename: The /proc filename to the control we're accessing
        valueRange: The number of useful readings out of the driver output
        """

        # Open in unbuffered r/w mode
        self._controlFile = open(controlFilename, u'r+', 0)
        self._valueRange = valueRange

    def _issueCommand(self, command, value=None):
        u"""
        Issues commands to the controller.
        """
        self._controlFile.write(u"%s %s" % (command, value))


    def readValues(self):
        u"""
        Creates a dictionary containing all useful key/value
        pairs defined by the valueRange. The rest of the output
        is usually just command descriptions. 
        """
        self._controlFile.seek(0);

        values = {}

        for x in range(0, self._valueRange):
            line = self._controlFile.readline();
            key, value = line.split(u':')
            values[key.strip()] = value.strip();

        return values;


class ToggleableControl(ThinkpadControl):
    u"""
    Generic singe,binary state control
    Has a little funky state handling, due to 
    inconsistences in the drivers' command/status naming
    """
    _controlValues = {}

    def __init__(self, controlFilename, controlValues):
        ThinkpadControl.__init__(self, controlFilename, 1)
        self._controlValues = controlValues

    def enable(self):
        print self._controlValues[u"on"]
        self._issueCommand(self._controlValues[u"on"])

    def disable(self):
        self._issueCommand(self._controlValues[u"off"])

    def toggle(self):
        u"""
        Toggles the status of the of the control, depending on its current state
        """
        if((self.readValues()[u"status"]).startswith(self._controlValues[u"on"])):
            self.disable()
        else:
            self.enable()

 

class FanControl(ThinkpadControl):
    u"""
    IBM ThikPad fan manipulation class. Performs fan speed readings
    and control on Linux machines, with properly enabled kernels.
    
    We don't inherit from the generic toggle-able class, because the
    enable command for the fan sets it in "auto" mode automatically. 
    
    Note: check for module experimental switch, it might be required. 
    
    """

    def __init__(self, controlFilename = u"/proc/acpi/ibm/fan"):
        ThinkpadControl.__init__(self, controlFilename, 3)
        
    
    def disable(self):
        self._issueCommand(u"disable")

    def enable(self):
        self._issueCommand(u"enable")

    def setSpeed(self, speedLevel):
        self._issueCommand(u"level", speedLevel)

    def setAuto(self):
        u"""
        Alias to .enable(). Enable automatically sets the
        speed level to "auto".
        """
        self.enable()

    def setDetached(self):
        self.setSpeed(u"detached")


class LEDControl(ThinkpadControl):
    u"""
    IBM ThikPad led manipulation class. Turns leds on/off/blinks. 

    Note: It's a bit ugly - passing led No as parameter. 
    TODO: Make prettier
    TODO: Check which ones seem to work for which models and make
    a model-based control. 
    
    """

    def __init__(self, controlFilename = u"/proc/acpi/ibm/led"):
        ThinkpadControl.__init__(self, controlFilename, 1)
        
    def ledCommand(self, ledNumber, command):
        self._issueCommand(ledNumber, command)

    def enable(self, ledNumber):
        self.ledCommand(ledNumber, u"on")

    def disable(self, ledNumber):
        self.ledCommand(ledNumber, u"off")

    def blink(self, ledNumber):
        self.ledCommand(ledNumber, u"blink")


class BeeperControl(ThinkpadControl):
    u"""
    Beeper Control

    I have NO clue whatsoever how this is supposed to work
    For now a value of 15 will enable a weird beep, most of 
    the times. Zero seems to be able to stop it. Has no other commands.
    """

    def __init__(self, controlFilename = u"/proc/acpi/ibm/beep"):
        ThinkpadControl.__init__(self, controlFilename, 1)
        

    def beep(self, beepLevel):
        self._issueCommand(beepLevel)




class LightControl(ToggleableControl):
    u"""
    Keyboard Light Control
    """
    def __init__(self, controlFilename = u"/proc/acpi/ibm/light"):
        ToggleableControl.__init__(self, controlFilename,
                                 {u"on":u"on", u"off":u"off"})


class BluetothControl(ToggleableControl):
    u"""
    Bluetooth Control
    """
    def __init__(self, controlFilename = u"/proc/acpi/ibm/bluetooth"):
        ToggleableControl.__init__(self, controlFilename,
                                 {u"on":u"enable", u"off":u"disable"})




class BrightnessControl(ThinkpadControl):
    u"""
    Screen Brightness Control
    """

    def __init__(self, controlFilename = u"/proc/acpi/ibm/brightness"):
        ThinkpadControl.__init__(self, controlFilename, 1)

    def setLevel(self, brightnessLevel):
        self._issueCommand(u"level", brightnessLevel)


    def up(self):
        self._issueCommand(u"up")

    def down(self):
        self._issueCommand(u"down")


class VolumeControl(ThinkpadControl):
    u"""
    VolumeControl - drivers seem to have a problem with direct setting of level
    and the OSD - the OSD level is not correctly set. 

    TODO: Enable a fix mode, where we'll iterate over values to enable the gauge
    to display correctly. 
    Note: Might be just an Ubuntu bug as well, check. 
    """

    def __init__(self, controlFilename = u"/proc/acpi/ibm/volume"):
        ThinkpadControl.__init__(self, controlFilename, 2)

    def setLevel(self, volumeLevel):
        self._issueCommand(u"level", volumeLevel)


    def mute(self):
        self._issueCommand(u"mute")

    def unmute(self):
        u"""
        Alias to .down(). Changing vol. in muted mode ignores the first
        increment/decrement
        """
        self.down()

    def up(self):
        self._issueCommand(u"up")

    def down(self):
        self._issueCommand(u"down")


class ThermalSensor():
    u"""
    This class provides output from ThinkPad thermal sensors.
    Since the format is somewhat differnt, it doesn't inherit
    the standard functionality.
    """

    _controlFile = None

    # Those are corrct value mappings for a ThinkPad X31. Most machines should
    # have the same ordering, but nevertheless - those need to be factored out
    # and loaded on-demand, depending on the machine. 
    _sensorMappings = [u"CPU", u"MiniPCI", u"HDD", u"GPU", u"Bat-1",
                       u"Unknown-1", u"Bat-2", u"Unknown-2"]
    
    def __init__(self, controlFilename = u"/proc/acpi/ibm/thermal"):
        self._controlFile = open(controlFilename, u"r", 0)

    def readValues(self):
        u"""
        Reads out all readings in a dict. 
        TODO: - Clean up Unknown/Unplugged entries?
        """
        self._controlFile.seek(0)

        valuesList = []
        values ={}

        key,valueLine = self._controlFile.readline().split(u":");
        valuesList = [int(value) for value in valueLine.split(u" ")]

        for count,value in enumerate(valuesList):
            values[self._sensorMappings[count]] = value

        return values

